package asteroids.model.programs.parsing.statements;


import java.util.*;
import asteroids.model.*;
import asteroids.model.programs.Program;
import asteroids.model.programs.parsing.ProgramFactory.ForeachType;
import asteroids.model.programs.parsing.expressions.*;
import asteroids.model.programs.parsing.types.*;

public class ExecuteManager {

	private Program program;
	private Map<String, E> map = new HashMap<String , E>();

	public ExecuteManager(Program program) {
		setProgram(program);
	}




	public void setProgram(Program program){
		this.program = program;
	}

	public Program getProgram(){
		return this.program;
	}

	public Ship getShip(){
		return getProgram().getShip();
	}

	private Map<String, E> getMap(){
		return this.map;
	}

	public E getFromMap(String key){
		return getMap().get(key);
	}

	private void addToMap(String key, E value){
		getMap().put(key, value);
	}

	public void execute(ForEachLoop forEachLoop){

		ForeachType type = forEachLoop.getType();
		//List<S> bodyStatements = new ArrayList<S>();

		S body = forEachLoop.getBody();
		//		if(body.getClass() == Sequence.class)
		//			bodyStatements = ((Sequence)body).getAllStatements();
		//		else
		//			bodyStatements.add(body);


		Set<CollisionObject> typeSet = new HashSet<CollisionObject>();


		if(type == ForeachType.ASTEROID){
			Set<Asteroid> allAsteroids	= getProgram().getShip().getWorld().getAsteroids();
			for (Asteroid asteroid : allAsteroids) {
				typeSet.add(asteroid);
			}
			executeForEachLoop(body, typeSet, forEachLoop);
		}
		else if(type == ForeachType.SHIP){
			Set<Ship> allShips = getProgram().getShip().getWorld().getShips();
			for(Ship ship:allShips){
				typeSet.add(ship);
			}
			executeForEachLoop(body, typeSet, forEachLoop);

		}

		else if(type == ForeachType.BULLET){
			Set<Bullet> allBullets = getProgram().getShip().getWorld().getBullets();
			for(Bullet bullet:allBullets){
				typeSet.add(bullet);
			}
			executeForEachLoop(body, typeSet, forEachLoop);

		}

		else if(type == ForeachType.ANY){
			typeSet = getProgram().getShip().getWorld().getGameObjects();
			executeForEachLoop(body, typeSet, forEachLoop);

		}
		else{
			System.out.println("There was a mistake in you're syntax, please try again");
			getProgram().setMistake();
		}
		if(!getProgram().isFinished()){
			forEachLoop.setExecuted();
			System.out.println("The for each is finished");
		}
	}

	private void executeForEachLoop(S body, Set<CollisionObject> typeSet, ForEachLoop forEachLoop ){
		int amountAsteroids 	= typeSet.size();								//the amount of collisionobjects in game					//the amount of statements to execute for each collisionobject
		body.redo();
		int amountAsteroidsExecuted=0;

		while(amountAsteroidsExecuted < amountAsteroids && !getProgram().getActionDone() && !forEachLoop.getExecuted()){						//Not all collisionobjects have completed the part of the program

			for(CollisionObject collisionObject:typeSet){

				//SAVING
				String key = forEachLoop.getVariableName();
				EntityType entityType = new EntityType();
				entityType.setEntity(collisionObject);
				Entity entity = new Entity(collisionObject);
				addToMap(forEachLoop.getVariableName(), entity);
				getProgram().getMap().put(key,entityType);

				//No actionstatement crossed
				boolean finished = false;										//Not yet all statements finished for this collisionobject

				while(!getProgram().getActionDone() && !finished){ 	

					body.execute(this);								//execute
					if(getProgram().getActionDone()){
						System.out.println("There was a mistake in you're syntax, please try again");
						getProgram().setMistake();
					}
					finished = true;										//Suppose all the statements have been executed by this asteroid

				}

				amountAsteroidsExecuted++;									//the statements for this part of the program have been finished for a collisionobject, increment now
				if(amountAsteroids >= amountAsteroidsExecuted){	//all the statements from the given body are executed
					forEachLoop.setLoopFinished(true);
					forEachLoop.setExecuted();
					finished = true;
				}
				body.redo();
			}
		}
	}




	public void execute(WhileLoop whileLoop){
		if(whileLoop.isGoodCondition(getMap(), getShip())){
			if(!whileLoop.getExecuted() && whileLoop.getCondition().getContent(getMap(), getShip())!= null){
				while((boolean)whileLoop.getCondition().getContent(getMap(), getShip()) && !getProgram().getActionDone() && !getProgram().getMistake()){
					whileLoop.getBody().execute(this);
					if(!getProgram().getActionDone() && whileLoop.getBody().getExecuted())
						whileLoop.redo();
				}
				if(!(boolean)whileLoop.getCondition().getContent(getMap(), getShip()) && !getProgram().getActionDone() || getProgram().getMistake())
					whileLoop.setExecuted();
			}
			else if(whileLoop.getCondition().getContent(getMap(), getShip())== null){
				whileLoop.setExecuted();	
			}
		}
		else{
			System.out.println("There was a mistake in you're syntax, please try again");
			getProgram().setMistake();
			whileLoop.setExecuted();
		}

	}



	public void execute2(WhileLoop whileLoop){
		if(!whileLoop.getExecuted() ){
			if(whileLoop.getCondition().isBoolean()){
				while((boolean)whileLoop.getCondition().getContent(getMap(), getShip())&& !getProgram().getActionDone()){
					whileLoop.getBody().execute(this);

				}
			}
			if(whileLoop.getCondition().isString()){
				E e = getFromMap((String)whileLoop.getCondition().getContent(getMap(), getShip()));
				while(	(boolean)e.getContent(getMap(), getShip()) && getProgram().getActionDone()){
					whileLoop.getBody().execute(this);

				}
			}
		}
		if(!(boolean)whileLoop.getCondition().getContent(getMap(), getShip()) && getProgram().getActionDone()){
			whileLoop.setExecuted();
		}
		whileLoop.getBody().redo();
	}





	public void execute(Assignment assignment){
		if(!assignment.getExecuted()){
			String key = assignment.getVariable();
			T type = getProgram().getMap().get(key);
			Object object =assignment.getRhs().getContent(getMap(), getShip());
			if(object != null){
				if(assignment.getRhs().isString()){
					E variable =null;
					if( object.getClass() == Double.class && type.getClass() == DoubleType.class){
						variable =new DoubleLiteral((double) assignment.getRhs().getContent(getMap(), getShip()));
						addToMap(key, variable);}
					else if(object.getClass() == Boolean.class && type.getClass() == BoolType.class){
						variable =new BooleanLiteral((boolean) assignment.getRhs().getContent(getMap(), getShip()));
						addToMap(key, variable);
					}
					else if(type.getClass() == EntityType.class){
						if(object.getClass() != double.class){
							variable =new Entity((CollisionObject) assignment.getRhs().getContent(getMap(), getShip()));
							addToMap(key, variable);
						}
					}
					else if(object.getClass() == double.class){
						E nullie = new Null();
						addToMap(key, nullie);
					}
					else{
						getProgram().setMistake();
						System.out.println("There was a mistake in you're syntax, please try again.");
					}
				}
				else 
					if(assignment.getRhs().isDouble() && type.getClass() == DoubleType.class){
						E doubleLiteral =new DoubleLiteral((double) object);
						addToMap(key, doubleLiteral);
					}



					else if(assignment.getRhs().isBoolean() && type.getClass() == BoolType.class){
						E booleanLiteral =new BooleanLiteral((boolean) object);
						addToMap(key, booleanLiteral);

					}
					else if(type.getClass() == EntityType.class){
						if(assignment.getRhs().isEntity() && assignment.getRhs().getContent(getMap(), getShip()).getClass() != Double.class ){
							E entity =new Entity((CollisionObject) object);
							addToMap(key, entity);
						}
						else if(assignment.getRhs().getContent(getMap(), getShip()).getClass() == Double.class ){
							E nullie = new Null();
							addToMap(key, nullie);	
						}
					}
					else{
						getProgram().setMistake();
						System.out.println("There was a mistake in you're syntax, please try again.");

					}
			}
			else{
				getProgram().setMistake();
			}
			if(!getProgram().isFinished()){
				assignment.setExecuted();
				System.out.println("Assignment completed");
			}
		}
	}

	public void execute(IfThenElse ifThenElse){
		if(ifThenElse.isGoodCondition(getMap(), getShip())){
			if(!ifThenElse.getExecuted() && !getProgram().getActionDone()){
				if(ifThenElse.getCondition().isBoolean()){
					boolean condition = (boolean)ifThenElse.getCondition().getContent(getMap(), getShip());
					if(condition)
						ifThenElse.getThen().execute(this);
					else
						ifThenElse.getOtherwise().execute(this);
				}
				if(ifThenElse.getThen().getExecuted() || ifThenElse.getOtherwise().getExecuted()){
					ifThenElse.setExecuted();
					System.out.println("If then else completed");
				}
			}
		}
		else{
			getProgram().setMistake();
			ifThenElse.setExecuted();
			System.out.println("There was a mistake in you're syntax, please try again.");

		}
	}

	public void execute(Print print){
		if(!print.getExecuted()){
			try{
				String printText = print.getExpressionToPrint().toString(getMap(),getShip());
				System.out.println(printText);			
				print.setExecuted();
				System.out.println("Printing completed");
			}
			catch(Exception exc){
				System.out.println("Unable to print the given expression.");
			}
		}
		print.setExecuted();
	}

	public void execute(Turn turn){
		if(!getProgram().getMistake()){
			if(!turn.getExecuted() && !getProgram().getActionDone() && turn.getAngle().getContent(getMap(), getShip())!= null){
				if(turn.getAngle().isDouble()){
					getShip().turn((double) turn.getAngle().getContent(getMap(), getShip()));
					getProgram().setActionDone(true);}
				else if(turn.getAngle().isString() && turn.getAngle().getContent(getMap(), getShip()).getClass() == Double.class){
					{
						getShip().turn((double)turn.getAngle().getContent(getMap(), getShip()));
						getProgram().setActionDone(true);}
				}
				else{
					System.out.println("There is a mistake in you're syntax, please try again.");
					getProgram().setMistake();
				}
			}
			if(!getProgram().isFinished()){
				turn.setExecuted();
				System.out.println("Turning completed");	
			}
		}
		else {
			System.out.println("Turning completed");
			turn.setExecuted();
		}
	}

	public void execute(Fire fire){
		if(!getProgram().getMistake()){
			if(!fire.getExecuted() && !getProgram().getActionDone()){
				try{
					getShip().fireBullet();
					System.out.println("Firing completed");		}
				catch(Exception exc){
					System.out.println("Fire failed. To many bullets");
				}
				fire.setExecuted();
				getProgram().setActionDone(true);
			}
		}
		else {
			System.out.println("Firing completed");
			fire.setExecuted();
		}
	}

	public void execute(Thrust thrust){
		if(!getProgram().getMistake()){
			if(!thrust.getExecuted()&& !getProgram().getActionDone()){
				boolean activationRequired = thrust.isToBeActivated();
				if(activationRequired){
					if(!getShip().isShipThrusterActive())
						getShip().setThrusterActive(activationRequired);
					System.out.println("Activating thruster completed");
				}
				else{
					if(getShip().isShipThrusterActive())
						getShip().setThrusterActive(activationRequired);
					System.out.println("Deactivating thruster completed");
				}
				getProgram().setActionDone(true);
				thrust.setExecuted();
			}
		}
		else{
			System.out.println("Thrusting completed");
			thrust.setExecuted();
		}
	}

	public void execute(Skip skip){
		//Do nothing
		if(!getProgram().getMistake()){
			getProgram().setActionDone(true);
		}
		System.out.println("Skip completed");
		skip.setExecuted();
	}




	public void execute(Sequence sequence){
		if(!sequence.getExecuted()){
			List<S> statements = sequence.getStatements();
			while((sequence.getNbOfExecutes() < statements.size()) && !getProgram().getActionDone()){
				S statement = statements.get(sequence.getLastExecute());
				if(!statement.getExecuted() ){
					statement.execute(this);
					if(statement.getExecuted()){
						sequence.incrementNbOfExecutes();
						sequence.incrementLastExecute();
					}

				}
			}
			if(sequence.getNbOfExecutes() == statements.size() ){
				sequence.setExecuted();
			}	

		}
	}
}

